package baseFx.common.collections.redis;

import baseFx.common.Func1T;
import baseFx.common.Stream;
import baseFx.common.collections.RemoteList;
import baseFx.common.utils.StringUtils;
import baseFx.serialization.json.JsonUtils;
import redis.clients.jedis.Jedis;

import java.util.*;

public class RedisList<E> implements RemoteList<E> {
    private Class<E> clazz;
    private String name;
    private Jedis jedis;

    public RedisList(Jedis jedis, Class<E> clazz, String name) {
        this.name = name;
        this.clazz = clazz;
        this.jedis = jedis;
    }

    protected String serialize(Object o) {
        return JsonUtils.toJson(o);
    }

    protected E deserialize(String content, Class<E> clazz) {
        return JsonUtils.toObject(content, clazz);
    }

    protected E deserialize(String content) {
        return deserialize(content, clazz);
    }

    @Override
    public int size() {
        return jedis.llen(name).intValue();
    }

    @Override
    public boolean isEmpty() {
        return size() < 1;
    }

    @Override
    public boolean contains(Object o) {
        Func1T<E, Boolean> func = (Func1T<E, Boolean>) o;
        if (func == null) {
            throw new RuntimeException("请使用 Func1T<E, Boolean>。");
        }
        int len = size();
        for (int i = 0; i < len; i++) {
            E item = get(i);
            if (func.execute(item)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            private int index = -1;
            private int size = RedisList.this.size();

            @Override
            public boolean hasNext() {
                return index + 1 < size;
            }

            @Override
            public E next() {
                return RedisList.this.get(++index);
            }
        };
    }

    @Override
    public Object[] toArray() {
        List<E> list = Stream.create(jedis.lrange(name, 0, size())).map(this::deserialize).toList();
        Object[] items = new Object[list.size()];
        Stream.create(list).foreach((i, n) -> {
            items[i] = n;
        });
        return items;
    }

    @Override
    public <T> T[] toArray(T[] a) {
        throw new RuntimeException("不支持此操作");
    }

    @Override
    public boolean add(E e) {
        return jedis.rpush(name, serialize(e)) > 0;
    }

    @Override
    public boolean remove(Object o) {
        return jedis.lrem(name, 0, serialize(o)) > 0;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        throw new RuntimeException("不支持此操作");
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        String[] values = new String[c.size()];
        Stream.create(c).map(this::serialize).foreach((i, n) -> {
            values[i] = n;
        });
        return jedis.rpush(name, values) > 0;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        String[] values = new String[c.size()];
        Stream.create(c).map(this::serialize).foreach((i, n) -> {
            values[i] = n;
        });
        if (index >= size()) {
            jedis.rpush(name, values);
        } else if (index == 0) {
            jedis.lpush(name, values);
        } else {
            List<String> list = jedis.lrange(name, index, -1);
            jedis.ltrim(name, 0, index - 1);
            String[] data = new String[values.length + list.size()];
            for (int i = 0; i < values.length; i++) {
                data[i] = values[i];
            }
            for (int i = 0; i < list.size(); i++) {
                data[i + values.length] = list.get(i);
            }
            jedis.rpush(name, data);
        }
        return true;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        List<String> items = Stream.create(c).map(this::serialize).toList();
        int count = 0;
        for (String item : items) {
            if (jedis.lrem(name, 0, item) > 0) {
                count++;
            }
        }
        return count > 0;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new RuntimeException("不支持的操作");
    }

    @Override
    public void clear() {
        jedis.del(name);
    }

    @Override
    public E get(int index) {
        String value = jedis.lindex(name, index);
        if (StringUtils.isNullOrEmpty(value) == false) {
            return deserialize(value);
        }
        return null;
    }

    @Override
    public E set(int index, E element) {
        jedis.lset(name, index, serialize(element));
        return element;
    }

    @Override
    public void add(int index, E element) {
        List<E> items = new ArrayList<>();
        items.add(element);
        addAll(index, items);
    }

    @Override
    public E remove(int index) {
        E item = get(index);
        if (item != null && remove(item)) {
            return item;
        }
        return null;
    }

    @Override
    public int indexOf(Object o) {
        Func1T<E, Boolean> func = (Func1T<E, Boolean>) o;
        if (func == null) {
            throw new RuntimeException("请使用 Func1T<E, Boolean>。");
        }
        Object[] items = toArray();
        for (int i = 0; i < items.length; i++) {
            if (func.execute((E) items[i])) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public int lastIndexOf(Object o) {
        Func1T<E, Boolean> func = (Func1T<E, Boolean>) o;
        if (func == null) {
            throw new RuntimeException("请使用 Func1T<E, Boolean>。");
        }
        Object[] items = toArray();
        for (int i = items.length - 1; i > -1; i--) {
            if (func.execute((E) items[i])) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return new RedisListIterator(index, size());
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        List<String> data = jedis.lrange(name, fromIndex, toIndex - 1);
        return Stream.create(data).map(this::deserialize).toList();
    }

    protected class RedisListIterator implements ListIterator<E> {
        private int size;
        private int curr;
        private Boolean isPrev;

        public RedisListIterator(int index, int size) {
            this.curr = index - 1;
            this.size = size;
        }

        @Override
        public boolean hasNext() {
            if (isPrev == null) {
                isPrev = false;
            }
            if (isPrev) {
                return false;
            }
            return nextIndex() < size;
        }

        @Override
        public E next() {
            return RedisList.this.get(++curr);
        }

        @Override
        public boolean hasPrevious() {
            if (isPrev == null) {
                isPrev = true;
                curr = size;
            }
            if (isPrev == false) {
                return false;
            }
            return previousIndex() > -1;
        }

        @Override
        public E previous() {
            return RedisList.this.get(--curr);
        }

        @Override
        public int nextIndex() {
            return curr + 1;
        }

        @Override
        public int previousIndex() {
            return curr - 1;
        }

        @Override
        public void remove() {
            if (hasPrevious() && RedisList.this.remove(previousIndex()) != null) {
                size--;
            } else if (hasNext() && RedisList.this.remove(nextIndex()) != null) {
                size--;
            }
        }

        @Override
        public void set(E e) {
            if (hasPrevious()) {
                RedisList.this.set(previousIndex(), e);
            } else if (hasNext()) {
                RedisList.this.set(nextIndex(), e);
            }
        }

        @Override
        public void add(E e) {
            if (hasPrevious()) {
                RedisList.this.add(previousIndex(), e);
            } else if (hasNext()) {
                RedisList.this.add(nextIndex(), e);
            }
        }
    }
}
